/*
 * BTDispatcher.java
 *
 * Created on 29 August 2007, 23:06
 *
 */

package digitrix.network.bt;

import digitrix.fightclub.game.FightClub;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.microedition.io.*;


/**
 *
 * @author Josh
 */
public class BTDispatcher implements Runnable {
    
    private static final int EVENT_QUEUE_SIZE = 20;
    private static final int EVT_TYPE = 0;
    private static final int EVT_DATA = 1;
    
    private int _code = -1;
    private int _previousDispatchedEvt = -1;
    private IBTCallback _callback = null;
    private DataOutputStream _outputStream = null;
    private boolean _running = false;
    private int _syncError = 0;    
    private Object _lock = null;
    
    // event queue for dispatching events
    private int[][] _evtQueue = null; // first array holds event type, second holds game data
    private int _curQueueIndex = 0;
    private int _curQueueSize = 0;
    
    // some temp variabels to hold before they're send across the wire    '
    private String _playerDetails = null;
    private int _posX = -1;
    private int _posY = -1;
    private int _health = -1;
    
    /**
     * Creates a new instance of BTListener
     */
    public BTDispatcher( IBTCallback callback, DataOutputStream outputStream ) {
        _callback = callback;
        _outputStream = outputStream;
        
        _evtQueue = new int[EVENT_QUEUE_SIZE][2];
        _curQueueIndex = 0;
        _curQueueSize = 0;
        
        _lock = new Object();
        
        startDispatcher();
    }
    
    private void startDispatcher(){
        _running = true;
        new Thread( this ).start();
    }
    
    public void stop(){
        _running = false;
        
        // wake up our worker
        synchronized ( _lock ) {
            _lock.notifyAll();
        }
        
        if (_outputStream != null){
            try{
                _outputStream.close();
            } catch( IOException ioe ){}
            _outputStream = null;
        }               
    }
    
    public boolean isAlive(){
        return _running;
    }
    
    public void run(){
        
        while( _running ){
            byte evt = 0;
            int data = 0; 
            
            if ( _curQueueIndex == _curQueueSize  ){
                try {
                    synchronized ( _lock ) {
                        _lock.wait();
                    }

                } catch (InterruptedException e) {
                    // do nothing
                }
            }
            
            FightClub.getInstance().printDebug( "BTDispatcher", "run", "Woken up!" ); 
            
            // check to see if we have an event on the queue
            if ( _curQueueIndex == _curQueueSize  )
                continue; // skip body of loop and go back to sleep
            
            // get the event and event details of the queue
            evt = (byte)_evtQueue[_curQueueIndex][EVT_TYPE];
            data = _evtQueue[_curQueueIndex][EVT_DATA]; 
            
            // increment _curQueueIndex 
            _curQueueIndex++; 
            if( _curQueueIndex >= _evtQueue.length )
                _curQueueIndex = 0; 
            
            try{
                switch( evt ){
                    // handle methods with no associated data
                    case BTManager.EVT_ACCEPT:
                    case BTManager.EVT_DECLINE:
                    case BTManager.EVT_END:
                    case BTManager.EVT_FIGHT:
                    case BTManager.EVT_GAME_OVER:
                    case BTManager.EVT_PAUSE:                    
                    case BTManager.EVT_RESTART:
                    case BTManager.EVT_SYNC: 
                    case BTManager.EVT_READY: 
                    case BTManager.EVT_LAG_TEST_REQ:
                    case BTManager.EVT_LAG_TEST_RES: 
                    case BTManager.EVT_LOCAL_LAG_TEST_FINISHED:{
                        _outputStream.writeByte( evt );
                        _outputStream.flush();
                        break;
                    }
                    case BTManager.EVT_GAME_DATA:
                    case BTManager.EVT_SYNC_ROUND_OVER:
                    case BTManager.EVT_SYNC_TIME:{
                        _code++;                        
                        _outputStream.writeByte( evt );
                        _outputStream.writeInt( _code );
                        _outputStream.writeInt( data ); 
                        _outputStream.flush();
                        break;
                    }
                    case BTManager.EVT_REQUEST_FIGHT:
                    case BTManager.EVT_PLAYER_INFO:{                    
                        // make sure we have the players details 
                        if( _playerDetails == null )
                            throw new Exception( "Player details have not been initilised" ); 
                        
                        _outputStream.writeByte( evt );
                        _outputStream.writeUTF( _playerDetails ); 
                        _outputStream.flush();
                        break;
                    }
                    case BTManager.EVT_SYNC_DETAILS:{
                        _outputStream.writeByte( evt );
                        _outputStream.writeInt( _posX );
                        _outputStream.writeInt( _posY );
                        _outputStream.writeInt( _health );
                        _outputStream.flush();                                                
                    }
                    case BTManager.EVT_SYNC_DATA:{
                        // TODO:
                        break;
                    }
                    
                }
                
            } catch(Exception ex){
                // an exception may occur becuase a connection was lost... what to do?
                // for this assignment I will just close the connection but really what should happen is the
                // the watcher should report back to the listener and notify them that the player has been
                // lost then the connection can be closed... This may have to be done... Soon see (probably only matters
                // for the host).
                System.err.print( "BTDispatcher.run() :: " + ex.toString() );
            }
        }
        
        BTManager.printDebugInfo( "BTDispatcher", "stop", "Dispatcher closed" ); 
    }
    
    public IBTCallback getCallback() {
        return _callback;
    }
    
    public void setCallback(IBTCallback callback) {
        this._callback = callback;
    }
    
    //<editor-fold defaultstate="collapsed" desc="dispatcher methods">
    public void dispatchGameData( int gameData ){
        addEvent( BTManager.EVT_GAME_DATA, gameData );
    }
    
    public void dispatchEvent( int evt, int data ){
        addEvent( evt, data ); 
    }        
    
    public void dispatchNonParamEvent( int evt ){
        if( evt == BTManager.EVT_PLAYER_INFO || evt == BTManager.EVT_ACCEPT )
            dispatchPlayerDetails( evt, _playerDetails );
        else if( evt != BTManager.EVT_GAME_DATA ) 
            addEvent( evt, -1 );
    }
    
    public void dispatchSyncPlayerDetails( int posX, int posY, int health ){
        _posX = posX;
        _posY = posY; 
        _health = health; 
        
        addEvent( BTManager.EVT_SYNC_DETAILS, -1 ); 
    }
    
    /**
     *
     * @param playerDetails: e.g. "name,playerid,strength,speed,fights"
     **/
    public void dispatchPlayerDetails( int evt, String playerDetails ){
        if( evt == BTManager.EVT_PLAYER_INFO || evt == BTManager.EVT_ACCEPT ){
            _playerDetails = playerDetails;        
            addEvent( BTManager.EVT_PLAYER_INFO, -1 ); 
        }
    }
    
    public void dispatchFightRequest( String playersName ){
        _playerDetails = playersName; 
        addEvent( BTManager.EVT_REQUEST_FIGHT, -1 ); 
    }
    
    private void addEvent( int evtType, int evtData ){
        BTManager.printErrorInfo( this, "addEvent", "Adding event " + evtType ); 
        
        // add event to queue
        _evtQueue[_curQueueSize][EVT_TYPE] = evtType;
        _evtQueue[_curQueueSize][EVT_DATA] = evtData;
        
        // update position of queue size
        _curQueueSize++;
        if( _curQueueSize == _evtQueue.length - 1 ){
            _curQueueSize = 0;
        }
        
        // wake up our worker
        synchronized ( _lock ) {
            _lock.notifyAll();
        }
    }
    
    //</editor-fold>
    
}
